#include "include.h"

client_t *clientInit(uint16_t port)
{
   client_t *c = malloc(sizeof(client_t));

   c->port = port;
   /*
    * Our peerId format is different from the existing trackers for a couple reasons.
    * Our identifying string is SE at the beginning. This is sort of a mix of shadow
    * (1 char at the beginning) and regular(dash + two characters).
    * After the identifying SE we put the version in the next two characters. The version
    * is in base66 so we can get 66^2 version numbers with those two characters. See
    * mystr.c -> numUrlStr for the base_66 format. After the version we stick the port
    * in two bytes for whatever reason. Finally we pad the rest with 12 random characters
    * which don't have to be url encoded (save some space in announces).
    * The port bit is really optional and could be replaced with random stuff.
    */
   char *peerId = malloc(20 + 1);
   char *ustrversion = numUrlStr(1, 2);
   char *ustrport = numUrlStr(port, 3);
   char *ustrrand = randUrlStr(13);
   sprintf(peerId,"se%s%s%s", ustrversion,ustrport, ustrrand);
   //	           SEvvpp--------------
   free(ustrversion); free(ustrport); free(ustrrand);
   c->peer_id = peerId;
   c->running = 1;

   c->torrents = createList();
   return c;
}

int clientAddTorrentFile(client_t *c, char *file)
{
   FILE *f = fopen(file, "rb");
   if(f == NULL)
   {
      loggerLog(LOG_ERR, "Could not open file %s.", file);
      return 0;
   }
   fseek(f, 0, SEEK_END);
   long len = ftell(f);
   rewind(f);
   char *bcode = malloc(len);
   int res = fread(bcode, len, 1, f);
   if(res == -1)
   {
      loggerLog(LOG_ERR, "Error reading torrent file %s.\n", file);
      return 0;
   }
   list_t *l = bdecode(bcode, len);
   free(bcode);
   if(l == NULL) 
      return 0;
   torrent_t *t = torrentInit(l);
   //beFree(l); //Can't do this yet; torrentInit is using pointers from this
   
   listLock(c->torrents);
   addObject(c->torrents, t);
   listRelease(c->torrents);
   return 1;
}

/*
 * This will set the state to "starting". The EventLoop
 * will take care of actually announcing and stuff
 */
int clientStartTorrent(client_t *c, torrent_t *t)
{
   //Turns out I don't need the client
   t->status = STAT_STARTING;
   loggerLog(LOG_DEBUG, "Setting torrent %s stat to STARTING.\n", torrentGetName(t));
   return 1;
}

int clientStopTorrent(client_t *c, torrent_t *t)
{
   t->status = STAT_STOPPED;
   loggerLog(LOG_DEBUG, "Setting torrent %s stat to STOPPED.\n", torrentGetName(t));
   return 1;
}

int clientPauseTorrent(client_t *c, torrent_t *t)
{
   t->status = STAT_PAUSED;
   loggerLog(LOG_DEBUG, "Setting torrent %s stat to PAUSED.\n", torrentGetName(t));
   return 1;
}

int clientResumeTorrent(client_t *c, torrent_t *t)
{
   t->status = STAT_STARTING;
   loggerLog(LOG_DEBUG, "Setting torrent %s stat to STARTING.\n", torrentGetName(t));
   return 1;
}

int clientRemoveTorrent(client_t *c, torrent_t *t)
{
   listLock(c->torrents);
   void *ret = removeObject(c->torrents, t);
   listRelease(c->torrents);
   if(ret != NULL)
   {
      loggerLog(LOG_DEBUG, "Removing torrent %s.\n", torrentGetName(t));
      return 1;
   }
   loggerLog(LOG_WARN, "Failed to remove torrent %s. Was not in client.\n", torrentGetName(t));
   return 0;
}

int clientForceAnnounce(client_t *c, torrent_t *t)
{
   return 0;
}


void *clientAwaitHandshake(client_t *c)
{
   int sock;
   sock = socket(AF_INET, SOCK_STREAM, 0);
   struct sockaddr_in addr;
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = INADDR_ANY;
   addr.sin_port = htons(c->port);
   bind(sock, (struct sockaddr *)&addr, sizeof(addr));
   listen(sock, 5);

   while(c->running)
   {
      int psock;
      struct sockaddr_in paddr;
      unsigned int alen = sizeof(struct sockaddr_in);
      psock = accept(sock, (struct sockaddr *)&paddr, &alen);

      // Get message length
      uint8_t pstrlen;
      recvData(psock, &pstrlen, 1);

      // Get pstr
      char *useless = malloc(pstrlen);
      recvData(psock, useless, pstrlen);
      free(useless);

      // Receive the infohash
      unsigned char *info_hash = malloc(20);
      recvData(psock, info_hash, 20);

      // Send our own handshake
      sendHandshake(psock, c->peer_id, info_hash);
      // Receive peer_id
      char peer_id[20];
      recvData(psock, peer_id, 20);
      if(peer_id == c->peer_id)
      {
         continue;
      }

      // Construct peer
      torrent_t *t;
      short found = 0;
      listLock(c->torrents);
      for(resetList(c->torrents);!endList(c->torrents);)
      {
         t = getObject(c->torrents);
         if(rawstrcmp(t->infohash, info_hash, 20))
         {
            found = 1;
            break;
         }
      }
      listRelease(c->torrents);
      if(!found)
      {
         loggerLog(LOG_WARN, "Peer did not have recognized infohash. Ignoring....\n");
         continue;
      }
      peer_t *npeer = peerInit(ceil((double)t->totalSize / (double)t->pieceLen), t->pieceLen);
      npeer->socket_descriptor = psock;
      memcpy(npeer->peer_id, peer_id, 20);

      // Give the client the peer so that it can figure out what to do.
      clientAddPeer(c, npeer, info_hash);
   }
   return NULL;
}

void clientAddPeer(client_t *c, peer_t *p, unsigned char *info_hash)
{
   listLock(c->torrents);
   resetList(c->torrents);
   while(!endList(c->torrents))
   {
      torrent_t *t = getObject(c->torrents);
      if(rawstrcmp(t->infohash, info_hash, 20))
      {
         loggerLog(LOG_INFO, "Adding peer to torrent %s.\n", torrentGetName(t));
         listLock(t->inactpeers);
         addObject(t->inactpeers, p);
         listRelease(t->inactpeers);
         listRelease(c->torrents);
         return;
      }
   }
   listRelease(c->torrents);
   loggerLog(LOG_WARN, "Peer tried to get torrent with info hash=");
   int i;
   for(i=0;i<20;i++)
      loggerLog(LOG_WARN, "%x", info_hash[i]);
   loggerLog(LOG_WARN," but we don't have it.\n");
}

void *clientEventLoop(client_t *c)
{
   
   /*if(pthread_create(&c->listenThread, NULL, (void *(*)(void*))&clientAwaitHandshake, (void *)c))
   {
      loggerLog(LOG_ERR, "Could not listen for clients.\n");
      exit(1);
   }*/

   while(c->running)
   {
      torrent_t *t;
      listLock(c->torrents);
      if(listIsEmpty(c->torrents))
      {
         listRelease(c->torrents);
	 continue;
      }

      list_t *torCopy = createList();
      for(resetList(c->torrents);!endList(c->torrents);)
         addObject(torCopy, getObject(c->torrents));
      listRelease(c->torrents);

      for(resetList(torCopy);!endList(torCopy);)
      {
         t = getObject(torCopy);
         switch(t->status)
         {
            case STAT_LEECHING:
               torrentRequestBlocks(t);
            case STAT_SEEDING:
               torrentSendPieces(t);


            //These two only announce. Announce will change their state.
            //COMPLETING could probably go up with seeding too
            case STAT_STARTING:
            case STAT_COMPLETING:

            case STAT_PAUSED:
               torrentAnnounceIfNeeded(t, c->peer_id, c->port);
               torrentKeepPeersAlive(t);
               torrentShufflePeers(t, c->peer_id);
               torrentCheckForData(t);
               break;

            //Stopping is a special state. Might indicate an exit. Might just be a stop. Either way, tracker should know asap.
            case STAT_STOPPING:
               torrentForceAnnounce(t, c->peer_id, c->port);
            case STAT_COMPLETE:
            case STAT_STOPPED:

               continue;
         }
      }
      clearList(torCopy);
      free(torCopy);
      /*
       * THIS IS A HACKY FIX AND SHOULD BE REMOVED FOR THE PUBLIC RELEASE
       * something needs to be done to not hit 100% cpu, but this limit download
       * artificially in some cases I think. Bad idea.
       */
      struct timespec ts;
      ts.tv_sec = 0;
      ts.tv_nsec = 20 * 1000000; //20 msec
      nanosleep(&ts, NULL);
   }

   //CLEAN UP
   //One final round of announces for up/down reporting.
   torrent_t *t;
   listLock(c->torrents);
   for(resetList(c->torrents);!endList(c->torrents);)
   {
      t = getObject(c->torrents);
      t->status = STAT_STOPPING;
      torrentForceAnnounce(t, c->peer_id, c->port);
   }
   listRelease(c->torrents);
   
   //pthread_join(c->listenThread, NULL);


   return NULL;
}

void clientExit(client_t *c)
{
   loggerLog(LOG_DEBUG,"Setting client to not running.\n");
   c->running = 0;
   // TODO Cleanup torrents, peers, etc
}

list_t *clientGetTorrents(client_t *c)
{
   return c->torrents;
}


